﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Meow.Core.Graphics
{
    public interface IMesh : ICloneable
    {
        public VertexBuffer VertexBuffer { get; }

        public IndexBuffer IndexBuffer { get; }

        public BoundingSphere BoundingSphere { get; }

        public bool Built
        {
            get;
        }

        public void Build();
        public void Dispose();
    }

    public class Mesh<T> : IDisposable, IMesh
        where T :struct, IVertexType
    {
        public enum PrimitiveTypes
        {
            Cube, Cylinder, Quad, Plane, Pyramid, Sphere, Torus
        }

        internal protected BoundingSphere boundingSphere;
        internal protected BoundingBox boundingBox;

        public BoundingSphere BoundingSphere => boundingSphere;


        protected T[] _vertices;
        private ushort[] _indices;
        private VertexBuffer _vertexBuffer;
        private IndexBuffer _indexBuffer;
        private bool _built;
        protected Vector3 size = Vector3.One;
        protected Vector2 repeatTexture = Vector2.One;
        protected bool invertFaces = false;
        internal bool _sharedData = false;

        public T[] Vertices
        {
            get { return _vertices; }
            set { _vertices = value; }
        }

        public ushort[] Indices
        {
            get { return _indices; }
            set { _indices = value; }
        }

        public VertexBuffer VertexBuffer
        {
            get { return _vertexBuffer; }
            internal protected set { _vertexBuffer = value; }
        }

        public IndexBuffer IndexBuffer
        {
            get { return _indexBuffer; }
            internal protected set { _indexBuffer = value; }
        }

        public bool Built
        {
            get { return _built; }
            internal protected set
            {
                _built = value;
                NotifyConstructionDone();
            }
        }

        public event Action ConstructionDone = null;

        public void NotifyConstructionDone() => ConstructionDone?.Invoke();

        protected virtual void CreateGeometry() { }


        public virtual void ComputeBoundingInfos(Vector3[] positions)
        {
            if (_vertices == null)
                return;

            var min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            var max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            for (int i = 0, l = positions.Length; i < l; i++)
            {
                min.X = Math.Min(positions[i].X, min.X);
                min.Y = Math.Min(positions[i].Y, min.Y);
                min.Z = Math.Min(positions[i].Z, min.Z);
                max.X = Math.Max(positions[i].X, max.X);
                max.Y = Math.Max(positions[i].Y, max.Y);
                max.Z = Math.Max(positions[i].Z, max.Z);
            }

            boundingBox.Min = min;
            boundingBox.Max = max;

            boundingSphere.Radius = (float)(boundingBox.Max - boundingBox.Min).Length() / 2.0f;
            boundingSphere.Center = (boundingBox.Max + boundingBox.Min) / 2;
        }

        protected virtual void CreateBuffers(GraphicsDevice device)
        {
            _vertexBuffer = new VertexBuffer(device, typeof(T), _vertices.Length, BufferUsage.WriteOnly);
            _vertexBuffer.SetData(_vertices);

            _indexBuffer = new IndexBuffer(device, IndexElementSize.SixteenBits, _indices.Length, BufferUsage.WriteOnly);
            _indexBuffer.SetData(_indices);
        }

        public virtual void Build()
        {
            if (_vertices?.Length == 0) return;

            Dispose();
            CreateGeometry();
            CreateBuffers(Application.GraphicsDevice);
            Built = true;
        }

        public void Dispose()
        {
            if (_sharedData)
                return;

            _vertexBuffer?.Dispose();
            _indexBuffer?.Dispose();
        }

        public object Clone()
        {
            var clone = (Mesh<T>)MemberwiseClone();

            if (Built)
                Build();

            return clone;
        }
    }
}
